#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/InnerObjectFormats.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STCurrency.h>
#include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STPathSet.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/detail/STVar.h>

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

namespace ripple {

STObject::STObject(STObject&& other)
    : STBase(other.getFName()), v_(std::move(other.v_)), mType(other.mType)
{
}

STObject::STObject(SField const& name) : STBase(name), mType(nullptr)
{
}

STObject::STObject(SOTemplate const& type, SField const& name) : STBase(name)
{
    set(type);
}

STObject::STObject(SOTemplate const& type, SerialIter& sit, SField const& name)
    : STBase(name)
{
    v_.reserve(type.size());
    set(sit);
    applyTemplate(type);  // May throw
}

STObject::STObject(SerialIter& sit, SField const& name, int depth) noexcept(
    false)
    : STBase(name), mType(nullptr)
{
    if (depth > 10)
        Throw<std::runtime_error>("Maximum nesting depth of STObject exceeded");
    set(sit, depth);
}

STObject
STObject::makeInnerObject(SField const& name)
{
    STObject obj{name};

    // The if is complicated because inner object templates were added in
    // two phases:
    //  1. If there are no available Rules, then always apply the template.
    //  2. fixInnerObjTemplate added templates to two AMM inner objects.
    //  3. fixInnerObjTemplate2 added templates to all remaining inner objects.
    std::optional<Rules> const& rules = getCurrentTransactionRules();
    bool const isAMMObj = name == sfAuctionSlot || name == sfVoteEntry;
    if (!rules || (rules->enabled(fixInnerObjTemplate) && isAMMObj) ||
        (rules->enabled(fixInnerObjTemplate2) && !isAMMObj))
    {
        if (SOTemplate const* elements =
                InnerObjectFormats::getInstance().findSOTemplateBySField(name))
            obj.set(*elements);
    }
    return obj;
}

STBase*
STObject::copy(std::size_t n, void* buf) const
{
    return emplace(n, buf, *this);
}

STBase*
STObject::move(std::size_t n, void* buf)
{
    return emplace(n, buf, std::move(*this));
}

SerializedTypeID
STObject::getSType() const
{
    return STI_OBJECT;
}

bool
STObject::isDefault() const
{
    return v_.empty();
}

void
STObject::add(Serializer& s) const
{
    add(s, withAllFields);  // just inner elements
}

STObject&
STObject::operator=(STObject&& other)
{
    setFName(other.getFName());
    mType = other.mType;
    v_ = std::move(other.v_);
    return *this;
}

void
STObject::set(SOTemplate const& type)
{
    v_.clear();
    v_.reserve(type.size());
    mType = &type;

    for (auto const& elem : type)
    {
        if (elem.style() != soeREQUIRED)
            v_.emplace_back(detail::nonPresentObject, elem.sField());
        else
            v_.emplace_back(detail::defaultObject, elem.sField());
    }
}

void
STObject::applyTemplate(SOTemplate const& type)
{
    auto throwFieldErr = [](std::string const& field, char const* description) {
        std::stringstream ss;
        ss << "Field '" << field << "' " << description;
        std::string text{ss.str()};
        JLOG(debugLog().error()) << "STObject::applyTemplate failed: " << text;
        Throw<FieldErr>(text);
    };

    mType = &type;
    decltype(v_) v;
    v.reserve(type.size());
    for (auto const& e : type)
    {
        auto const iter =
            std::find_if(v_.begin(), v_.end(), [&](detail::STVar const& b) {
                return b.get().getFName() == e.sField();
            });
        if (iter != v_.end())
        {
            if ((e.style() == soeDEFAULT) && iter->get().isDefault())
            {
                throwFieldErr(
                    e.sField().fieldName,
                    "may not be explicitly set to default.");
            }
            v.emplace_back(std::move(*iter));
            v_.erase(iter);
        }
        else
        {
            if (e.style() == soeREQUIRED)
            {
                throwFieldErr(e.sField().fieldName, "is required but missing.");
            }
            v.emplace_back(detail::nonPresentObject, e.sField());
        }
    }
    for (auto const& e : v_)
    {
        // Anything left over in the object must be discardable
        if (!e->getFName().isDiscardable())
        {
            throwFieldErr(
                e->getFName().getName(), "found in disallowed location.");
        }
    }
    // Swap the template matching data in for the old data,
    // freeing any leftover junk
    v_.swap(v);
}

void
STObject::applyTemplateFromSField(SField const& sField)
{
    SOTemplate const* elements =
        InnerObjectFormats::getInstance().findSOTemplateBySField(sField);
    if (elements)
        applyTemplate(*elements);  // May throw
}

// return true = terminated with end-of-object
bool
STObject::set(SerialIter& sit, int depth)
{
    bool reachedEndOfObject = false;

    v_.clear();

    // Consume data in the pipe until we run out or reach the end
    while (!sit.empty())
    {
        int type;
        int field;

        // Get the metadata for the next field
        sit.getFieldID(type, field);

        // The object termination marker has been found and the termination
        // marker has been consumed. Done deserializing.
        if (type == STI_OBJECT && field == 1)
        {
            reachedEndOfObject = true;
            break;
        }

        if (type == STI_ARRAY && field == 1)
        {
            JLOG(debugLog().error())
                << "Encountered object with embedded end-of-array marker";
            Throw<std::runtime_error>("Illegal end-of-array marker in object");
        }

        auto const& fn = SField::getField(type, field);

        if (fn.isInvalid())
        {
            JLOG(debugLog().error()) << "Unknown field: field_type=" << type
                                     << ", field_name=" << field;
            Throw<std::runtime_error>("Unknown field");
        }

        // Unflatten the field
        v_.emplace_back(sit, fn, depth + 1);

        // If the object type has a known SOTemplate then set it.
        if (auto const obj = dynamic_cast<STObject*>(&(v_.back().get())))
            obj->applyTemplateFromSField(fn);  // May throw
    }

    // We want to ensure that the deserialized object does not contain any
    // duplicate fields. This is a key invariant:
    auto const sf = getSortedFields(*this, withAllFields);

    auto const dup = std::adjacent_find(
        sf.cbegin(), sf.cend(), [](STBase const* lhs, STBase const* rhs) {
            return lhs->getFName() == rhs->getFName();
        });

    if (dup != sf.cend())
        Throw<std::runtime_error>("Duplicate field detected");

    return reachedEndOfObject;
}

bool
STObject::hasMatchingEntry(STBase const& t)
{
    STBase const* o = peekAtPField(t.getFName());

    if (!o)
        return false;

    return t == *o;
}

std::string
STObject::getFullText() const
{
    std::string ret;
    bool first = true;

    if (getFName().hasName())
    {
        ret = getFName().getName();
        ret += " = {";
    }
    else
        ret = "{";

    for (auto const& elem : v_)
    {
        if (elem->getSType() != STI_NOTPRESENT)
        {
            if (!first)
                ret += ", ";
            else
                first = false;

            ret += elem->getFullText();
        }
    }

    ret += "}";
    return ret;
}

std::string
STObject::getText() const
{
    std::string ret = "{";
    bool first = false;
    for (auto const& elem : v_)
    {
        if (!first)
        {
            ret += ", ";
            first = false;
        }

        ret += elem->getText();
    }
    ret += "}";
    return ret;
}

bool
STObject::isEquivalent(STBase const& t) const
{
    STObject const* v = dynamic_cast<STObject const*>(&t);

    if (!v)
        return false;

    if (mType != nullptr && v->mType == mType)
    {
        return std::equal(
            begin(),
            end(),
            v->begin(),
            v->end(),
            [](STBase const& st1, STBase const& st2) {
                return (st1.getSType() == st2.getSType()) &&
                    st1.isEquivalent(st2);
            });
    }

    auto const sf1 = getSortedFields(*this, withAllFields);
    auto const sf2 = getSortedFields(*v, withAllFields);

    return std::equal(
        sf1.begin(),
        sf1.end(),
        sf2.begin(),
        sf2.end(),
        [](STBase const* st1, STBase const* st2) {
            return (st1->getSType() == st2->getSType()) &&
                st1->isEquivalent(*st2);
        });
}

uint256
STObject::getHash(HashPrefix prefix) const
{
    Serializer s;
    s.add32(prefix);
    add(s, withAllFields);
    return s.getSHA512Half();
}

uint256
STObject::getSigningHash(HashPrefix prefix) const
{
    Serializer s;
    s.add32(prefix);
    add(s, omitSigningFields);
    return s.getSHA512Half();
}

int
STObject::getFieldIndex(SField const& field) const
{
    if (mType != nullptr)
        return mType->getIndex(field);

    int i = 0;
    for (auto const& elem : v_)
    {
        if (elem->getFName() == field)
            return i;
        ++i;
    }
    return -1;
}

STBase const&
STObject::peekAtField(SField const& field) const
{
    int index = getFieldIndex(field);

    if (index == -1)
        throwFieldNotFound(field);

    return peekAtIndex(index);
}

STBase&
STObject::getField(SField const& field)
{
    int index = getFieldIndex(field);

    if (index == -1)
        throwFieldNotFound(field);

    return getIndex(index);
}

SField const&
STObject::getFieldSType(int index) const
{
    return v_[index]->getFName();
}

STBase const*
STObject::peekAtPField(SField const& field) const
{
    int index = getFieldIndex(field);

    if (index == -1)
        return nullptr;

    return peekAtPIndex(index);
}

STBase*
STObject::getPField(SField const& field, bool createOkay)
{
    int index = getFieldIndex(field);

    if (index == -1)
    {
        if (createOkay && isFree())
            return getPIndex(emplace_back(detail::defaultObject, field));

        return nullptr;
    }

    return getPIndex(index);
}

bool
STObject::isFieldPresent(SField const& field) const
{
    int index = getFieldIndex(field);

    if (index == -1)
        return false;

    return peekAtIndex(index).getSType() != STI_NOTPRESENT;
}

STObject&
STObject::peekFieldObject(SField const& field)
{
    return peekField<STObject>(field);
}

STArray&
STObject::peekFieldArray(SField const& field)
{
    return peekField<STArray>(field);
}

bool
STObject::setFlag(std::uint32_t f)
{
    STUInt32* t = dynamic_cast<STUInt32*>(getPField(sfFlags, true));

    if (!t)
        return false;

    t->setValue(t->value() | f);
    return true;
}

bool
STObject::clearFlag(std::uint32_t f)
{
    STUInt32* t = dynamic_cast<STUInt32*>(getPField(sfFlags));

    if (!t)
        return false;

    t->setValue(t->value() & ~f);
    return true;
}

bool
STObject::isFlag(std::uint32_t f) const
{
    return (getFlags() & f) == f;
}

std::uint32_t
STObject::getFlags(void) const
{
    STUInt32 const* t = dynamic_cast<STUInt32 const*>(peekAtPField(sfFlags));

    if (!t)
        return 0;

    return t->value();
}

STBase*
STObject::makeFieldPresent(SField const& field)
{
    int index = getFieldIndex(field);

    if (index == -1)
    {
        if (!isFree())
            throwFieldNotFound(field);

        return getPIndex(emplace_back(detail::nonPresentObject, field));
    }

    STBase* f = getPIndex(index);

    if (f->getSType() != STI_NOTPRESENT)
        return f;

    v_[index] = detail::STVar(detail::defaultObject, f->getFName());
    return getPIndex(index);
}

void
STObject::makeFieldAbsent(SField const& field)
{
    int index = getFieldIndex(field);

    if (index == -1)
        throwFieldNotFound(field);

    STBase const& f = peekAtIndex(index);

    if (f.getSType() == STI_NOTPRESENT)
        return;
    v_[index] = detail::STVar(detail::nonPresentObject, f.getFName());
}

bool
STObject::delField(SField const& field)
{
    int index = getFieldIndex(field);

    if (index == -1)
        return false;

    delField(index);
    return true;
}

void
STObject::delField(int index)
{
    v_.erase(v_.begin() + index);
}

unsigned char
STObject::getFieldU8(SField const& field) const
{
    return getFieldByValue<STUInt8>(field);
}

std::uint16_t
STObject::getFieldU16(SField const& field) const
{
    return getFieldByValue<STUInt16>(field);
}

std::uint32_t
STObject::getFieldU32(SField const& field) const
{
    return getFieldByValue<STUInt32>(field);
}

std::uint64_t
STObject::getFieldU64(SField const& field) const
{
    return getFieldByValue<STUInt64>(field);
}

uint128
STObject::getFieldH128(SField const& field) const
{
    return getFieldByValue<STUInt128>(field);
}

uint160
STObject::getFieldH160(SField const& field) const
{
    return getFieldByValue<STUInt160>(field);
}

uint192
STObject::getFieldH192(SField const& field) const
{
    return getFieldByValue<STUInt192>(field);
}

uint256
STObject::getFieldH256(SField const& field) const
{
    return getFieldByValue<STUInt256>(field);
}

std::int32_t
STObject::getFieldI32(SField const& field) const
{
    return getFieldByValue<STInt32>(field);
}

AccountID
STObject::getAccountID(SField const& field) const
{
    return getFieldByValue<STAccount>(field);
}

Blob
STObject::getFieldVL(SField const& field) const
{
    STBlob empty;
    STBlob const& b = getFieldByConstRef<STBlob>(field, empty);
    return Blob(b.data(), b.data() + b.size());
}

STAmount const&
STObject::getFieldAmount(SField const& field) const
{
    static STAmount const empty{};
    return getFieldByConstRef<STAmount>(field, empty);
}

STPathSet const&
STObject::getFieldPathSet(SField const& field) const
{
    static STPathSet const empty{};
    return getFieldByConstRef<STPathSet>(field, empty);
}

STVector256 const&
STObject::getFieldV256(SField const& field) const
{
    static STVector256 const empty{};
    return getFieldByConstRef<STVector256>(field, empty);
}

STObject
STObject::getFieldObject(SField const& field) const
{
    STObject const empty{field};
    auto ret = getFieldByConstRef<STObject>(field, empty);
    if (ret != empty)
        ret.applyTemplateFromSField(field);
    return ret;
}

STArray const&
STObject::getFieldArray(SField const& field) const
{
    static STArray const empty{};
    return getFieldByConstRef<STArray>(field, empty);
}

STCurrency const&
STObject::getFieldCurrency(SField const& field) const
{
    static STCurrency const empty{};
    return getFieldByConstRef<STCurrency>(field, empty);
}

STNumber const&
STObject::getFieldNumber(SField const& field) const
{
    static STNumber const empty{};
    return getFieldByConstRef<STNumber>(field, empty);
}

void
STObject::set(std::unique_ptr<STBase> v)
{
    set(std::move(*v.get()));
}

void
STObject::set(STBase&& v)
{
    auto const i = getFieldIndex(v.getFName());
    if (i != -1)
    {
        v_[i] = std::move(v);
    }
    else
    {
        if (!isFree())
            Throw<std::runtime_error>("missing field in templated STObject");
        v_.emplace_back(std::move(v));
    }
}

void
STObject::setFieldU8(SField const& field, unsigned char v)
{
    setFieldUsingSetValue<STUInt8>(field, v);
}

void
STObject::setFieldU16(SField const& field, std::uint16_t v)
{
    setFieldUsingSetValue<STUInt16>(field, v);
}

void
STObject::setFieldU32(SField const& field, std::uint32_t v)
{
    setFieldUsingSetValue<STUInt32>(field, v);
}

void
STObject::setFieldU64(SField const& field, std::uint64_t v)
{
    setFieldUsingSetValue<STUInt64>(field, v);
}

void
STObject::setFieldH128(SField const& field, uint128 const& v)
{
    setFieldUsingSetValue<STUInt128>(field, v);
}

void
STObject::setFieldH256(SField const& field, uint256 const& v)
{
    setFieldUsingSetValue<STUInt256>(field, v);
}

void
STObject::setFieldI32(SField const& field, std::int32_t v)
{
    setFieldUsingSetValue<STInt32>(field, v);
}

void
STObject::setFieldV256(SField const& field, STVector256 const& v)
{
    setFieldUsingSetValue<STVector256>(field, v);
}

void
STObject::setAccountID(SField const& field, AccountID const& v)
{
    setFieldUsingSetValue<STAccount>(field, v);
}

void
STObject::setFieldVL(SField const& field, Blob const& v)
{
    setFieldUsingSetValue<STBlob>(field, Buffer(v.data(), v.size()));
}

void
STObject::setFieldVL(SField const& field, Slice const& s)
{
    setFieldUsingSetValue<STBlob>(field, Buffer(s.data(), s.size()));
}

void
STObject::setFieldAmount(SField const& field, STAmount const& v)
{
    setFieldUsingAssignment(field, v);
}

void
STObject::setFieldCurrency(SField const& field, STCurrency const& v)
{
    setFieldUsingAssignment(field, v);
}

void
STObject::setFieldIssue(SField const& field, STIssue const& v)
{
    setFieldUsingAssignment(field, v);
}

void
STObject::setFieldNumber(SField const& field, STNumber const& v)
{
    setFieldUsingAssignment(field, v);
}

void
STObject::setFieldPathSet(SField const& field, STPathSet const& v)
{
    setFieldUsingAssignment(field, v);
}

void
STObject::setFieldArray(SField const& field, STArray const& v)
{
    setFieldUsingAssignment(field, v);
}

void
STObject::setFieldObject(SField const& field, STObject const& v)
{
    setFieldUsingAssignment(field, v);
}

Json::Value
STObject::getJson(JsonOptions options) const
{
    Json::Value ret(Json::objectValue);

    for (auto const& elem : v_)
    {
        if (elem->getSType() != STI_NOTPRESENT)
            ret[elem->getFName().getJsonName()] = elem->getJson(options);
    }
    return ret;
}

bool
STObject::operator==(STObject const& obj) const
{
    // This is not particularly efficient, and only compares data elements
    // with binary representations
    int matches = 0;
    for (auto const& t1 : v_)
    {
        if ((t1->getSType() != STI_NOTPRESENT) && t1->getFName().isBinary())
        {
            // each present field must have a matching field
            bool match = false;
            for (auto const& t2 : obj.v_)
            {
                if (t1->getFName() == t2->getFName())
                {
                    if (t2 != t1)
                        return false;

                    match = true;
                    ++matches;
                    break;
                }
            }

            if (!match)
                return false;
        }
    }

    int fields = 0;
    for (auto const& t2 : obj.v_)
    {
        if ((t2->getSType() != STI_NOTPRESENT) && t2->getFName().isBinary())
            ++fields;
    }

    if (fields != matches)
        return false;

    return true;
}

void
STObject::add(Serializer& s, WhichFields whichFields) const
{
    // Depending on whichFields, signing fields are either serialized or
    // not.  Then fields are added to the Serializer sorted by fieldCode.
    std::vector<STBase const*> const fields{
        getSortedFields(*this, whichFields)};

    // insert sorted
    for (STBase const* const field : fields)
    {
        // When we serialize an object inside another object,
        // the type associated by rule with this field name
        // must be OBJECT, or the object cannot be deserialized
        SerializedTypeID const sType{field->getSType()};
        XRPL_ASSERT(
            (sType != STI_OBJECT) ||
                (field->getFName().fieldType == STI_OBJECT),
            "ripple::STObject::add : valid field type");
        field->addFieldID(s);
        field->add(s);
        if (sType == STI_ARRAY || sType == STI_OBJECT)
            s.addFieldID(sType, 1);
    }
}

std::vector<STBase const*>
STObject::getSortedFields(STObject const& objToSort, WhichFields whichFields)
{
    std::vector<STBase const*> sf;
    sf.reserve(objToSort.getCount());

    // Choose the fields that we need to sort.
    for (detail::STVar const& elem : objToSort.v_)
    {
        STBase const& base = elem.get();
        if ((base.getSType() != STI_NOTPRESENT) &&
            base.getFName().shouldInclude(whichFields))
        {
            sf.push_back(&base);
        }
    }

    // Sort the fields by fieldCode.
    std::sort(sf.begin(), sf.end(), [](STBase const* lhs, STBase const* rhs) {
        return lhs->getFName().fieldCode < rhs->getFName().fieldCode;
    });

    return sf;
}

}  // namespace ripple
